package api

import (
	"moss/conf"
	"moss/meta"
	"moss/proto"
	"moss/status"
	"strings"
)

func GetService(requestId string, accessKeyId string) (*proto.ListAllMyBucketsResult, status.Status) {
	var bucketList proto.ListAllMyBucketsResult
	authInfo, ok := meta.GetAuthInfo(accessKeyId)
	if !ok {
		return nil, status.InvalidArgument
	}

	bucketList.Owner.ID = authInfo.AuthID
	bucketList.Owner.DisplayName = authInfo.DisplayName

	allBuckets := meta.GetMyBucketsInfo(accessKeyId)
	if allBuckets == nil {
		return &bucketList, status.Success
	}

	for _, v := range allBuckets {
		entry := proto.Bucket{
			CreationDate: v.CreationDate,
			Name:         v.Name}
		bucketList.Buckets.Bucket = append(bucketList.Buckets.Bucket, entry)
	}

	return &bucketList, status.Success
}

func PutBucket(requestId string, accessKeyId string, bucketName string, permission string, location string) status.Status {
	if !validDataCenter(location) {
		return status.InvalidLocationConstraint
	}

	if !validPermissionForBucket(permission) {
		return status.InvalidArgument
	}

	if !validIdentifier(bucketName) {
		return status.InvalidBucketName
	}

	bucketInfo, ok := meta.GetBucketInfo(bucketName)
	if ok {
		if bucketInfo.Location != location || bucketInfo.OwnerAccessKeyId != accessKeyId {
			return status.BucketAlreadyExists
		}
		if bucketInfo.Permission != permission {
			meta.SetBucketPermission(bucketInfo, permission)
		}
	} else {
		if meta.CountBuckets(accessKeyId) > conf.Config.Limit.MaxBucketCount {
			return status.TooManyBuckets
		}
		if !validIdentifier(bucketName) {
			return status.InvalidBucketName
		}
		meta.CreateBucket(bucketName, location, accessKeyId, permission)
	}

	return status.Success
}

func PutBucketAcl(requestId string, accessKeyId string, bucketName string, permission string) status.Status {
	if !validIdentifier(bucketName) {
		return status.InvalidBucketName
	}

	bucketInfo, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return status.NoSuchBucket
	}

	if bucketInfo.OwnerAccessKeyId != accessKeyId {
		return status.AccessDenied
	}

	// Bucket permission unchanged when x-oss-acl header not found
	if len(permission) == 0 {
		return status.Success
	}

	if !validPermission(permission) {
		return status.InvalidArgument
	}

	if bucketInfo.Permission != permission {
		meta.SetBucketPermission(bucketInfo, permission)
	}

	return status.Success
}

func GetBucket(requestId string, accessKeyId string, bucketName string, delimiter string, marker string, maxKeys int, prefix string) (*proto.ListBucketResult, status.Status) {
	if !validIdentifier(bucketName) {
		return nil, status.InvalidBucketName
	}

	bucketMeta, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return nil, status.NoSuchBucket
	}
	if !checkPermission(bucketMeta.Permission, bucketMeta.OwnerAccessKeyId, accessKeyId, conf.AccessTypeRead) {
		return nil, status.AccessDenied
	}
	if maxKeys < 0 || maxKeys > 1000 {
		return nil, status.InvalidArgument
	}
	if len(prefix) >= conf.ParameterMaxLength {
		return nil, status.InvalidArgument
	}
	if len(marker) >= conf.ParameterMaxLength {
		return nil, status.InvalidArgument
	}

	objetcList := meta.GetAllObjects(bucketName, prefix)

	var objectInfo proto.ListBucketResult
	insertedKeys := 0

	objectInfo.Name = bucketName
	objectInfo.Delimiter = delimiter
	objectInfo.Marker = marker
	objectInfo.MaxKeys = maxKeys
	objectInfo.Prefix = prefix
	objectInfo.IsTruncated = false

	if objetcList == nil {
		return &objectInfo, status.Success
	}

	for _, v := range objetcList {
		if len(marker) != 0 && v.Key < marker {
			continue
		}

		if insertedKeys == maxKeys {
			objectInfo.NextMarker = v.Key
			objectInfo.IsTruncated = true
			break
		}

		keyWithoutPrefix := strings.TrimPrefix(v.Key, prefix)
		if strings.Contains(keyWithoutPrefix, "/") && delimiter == "/" {
			// Key is a directory (conf prefix)
			thisPrefix := prefix + keyWithoutPrefix[:strings.Index(keyWithoutPrefix, "/")]
			objectInfo.CommonPrefixes.Prefix = append(objectInfo.CommonPrefixes.Prefix, thisPrefix)
		} else {
			entry := proto.Contents{
				Key:          v.Key,
				LastModified: v.LastModified,
				ETag:         v.ETag,
				Type:         v.Type,
				Size:         v.Size,
				StorageClass: v.StorageClass}

			authInfo, _ := meta.GetAuthInfo(v.OwnerAccessKeyId)
			entry.Owner.ID, entry.Owner.DisplayName = authInfo.AuthID, authInfo.DisplayName

			objectInfo.Contents = append(objectInfo.Contents, entry)
		}

		insertedKeys++
	}

	objectInfo.KeyCount = insertedKeys
	return &objectInfo, status.Success
}

func GetBucketAcl(requestId string, accessKeyId string, bucketName string) (*proto.AccessControlPolicy, status.Status) {
	if !validIdentifier(bucketName) {
		return nil, status.InvalidBucketName
	}

	bucketInfo, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return nil, status.NoSuchBucket
	}

	if bucketInfo.OwnerAccessKeyId != accessKeyId {
		return nil, status.AccessDenied
	}

	var acl proto.AccessControlPolicy
	authInfo, ok := meta.GetAuthInfo(bucketInfo.OwnerAccessKeyId)
	if !ok {
		return nil, status.InternalError
	}

	acl.Owner.ID = authInfo.AuthID
	acl.Owner.DisplayName = authInfo.DisplayName
	acl.AccessControlList.Grant = bucketInfo.Permission

	return &acl, status.Success
}

func GetBucketLocation(requestId string, accessKeyId string, bucketName string) (*proto.LocationConstraint, status.Status) {
	if !validIdentifier(bucketName) {
		return nil, status.InvalidBucketName
	}

	bucketMeta, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return nil, status.NoSuchBucket
	}

	if bucketMeta.OwnerAccessKeyId != accessKeyId {
		return nil, status.AccessDenied
	}

	var location proto.LocationConstraint
	location.Location = bucketMeta.Location
	return &location, status.Success
}

func DeleteBucket(requestId string, accessKeyId string, bucketName string) status.Status {
	if !validIdentifier(bucketName) {
		return status.InvalidBucketName
	}

	bucketMeta, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return status.NoSuchBucket
	}

	if meta.CountObjects(bucketName) != 0 {
		return status.BucketNotEmpty
	}

	if bucketMeta.OwnerAccessKeyId != accessKeyId {
		return status.AccessDenied
	}

	// dependencies
	_, flag := meta.GetLogging(bucketName)
	if flag {
		meta.DeleteLogging(bucketName)
	}

	_, flag = meta.GetBucketReferer(bucketName)
	if flag {
		meta.DeleteRefererList(bucketName)
	}

	_, flag = meta.GetWebsite(bucketName)
	if flag {
		meta.DeleteWebsite(bucketName)
	}

	meta.DeleteBucket(bucketName, accessKeyId)
	return status.Success
}

func HeadBucket(requestId string, accessKeyId string, bucketName string) status.Status {
	if !validIdentifier(bucketName) {
		return status.InvalidBucketName
	}

	bucketMeta, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return status.NoSuchBucket
	}

	if !checkPermission(bucketMeta.Permission, bucketMeta.OwnerAccessKeyId, accessKeyId, conf.AccessTypeRead) {
		return status.AccessDenied
	}

	return status.Success
}

func GetBucketInfo(requestId string, accessKeyId string, bucketName string) (*proto.BucketInfo, status.Status) {
	if !validIdentifier(bucketName) {
		return nil, status.InvalidBucketName
	}

	bucketMeta, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return nil, status.NoSuchBucket
	}

	if bucketMeta.OwnerAccessKeyId != accessKeyId {
		return nil, status.AccessDenied
	}

	var bucketInfo proto.BucketInfo
	bucketInfo.Bucket.CreationDate = bucketMeta.CreationDate
	authInfo, _ := meta.GetAuthInfo(bucketMeta.OwnerAccessKeyId)
	bucketInfo.Bucket.Owner.ID, bucketInfo.Bucket.Owner.DisplayName = authInfo.AuthID, authInfo.DisplayName
	bucketInfo.Bucket.AccessControlList.Grant = bucketMeta.Permission
	bucketInfo.Bucket.Location = bucketMeta.Location
	bucketInfo.Bucket.Name = bucketName
	// bucketInfo.Bucket.ExtranetEndpoint = meta.GetExtranetEndpoint(bucketName, bucketMeta.Location)
	// bucketInfo.Bucket.IntranetEndpoint = db.GetIntranetEndpoint(bucketName, bucketMeta.Location)

	return &bucketInfo, status.Success
}

